package com.wonder.framework.aws.service.impl;

import com.wonder.common.common.AjaxResult;
import com.wonder.common.constants.Constants;
import com.wonder.common.utils.StringUtils;
import com.wonder.framework.aws.service.AwsS3Service;
import com.wonder.framework.aws.utils.FileUtil;
import com.wonder.framework.aws.vo.AwsS3ObjectVO;
import com.wonder.framework.redis.RedisCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AwsS3ServiceImpl implements AwsS3Service {
    @Resource
    private S3Client s3Client;

    @Resource
    private S3Presigner s3Presigner;

    @Autowired
    private RedisCache redisTemplate;

    @Value("${aws-s3.filePrefix}")
    private String filePrefix;

    @Value("${aws-s3.bucketName}")
    private String bucket;


    /**
     * 上传对象
     * @param file 文件对象
     */
    @Override
    public String upload(MultipartFile file){

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String fileName = file.getOriginalFilename();
        fileName = UUID.randomUUID().toString().trim().replaceAll("-", "")+fileName;
        String filePath = sdf.format(new Date()) + "/" + fileName;
        long size = file.getSize();
        int mb = 1024 * 1024;
        try{
            // 如果文件大于100M则分片上传
            if(size < 100 * mb){
                singleUpload(bucket, filePath, file.getBytes());
            } else {
                multiUpload(bucket, filePath, file.getBytes());
            }

            return filePrefix+filePath;
        } catch (Exception ex){
            return "";
        }
    }


    /**
     * 上传对象
     * @param bucket bucket
     * @param file 文件对象
     */
    @Override
    public String upload(String bucket, MultipartFile file){

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String fileName = UUID.randomUUID().toString().trim().replaceAll("-", "");
        String filePath = sdf.format(new Date()) + "/" + fileName;
        long size = file.getSize();
        int mb = 1024 * 1024;
        try{
            // 如果文件大于100M则分片上传
            if(size < 100 * mb){
                singleUpload(bucket, filePath, file.getBytes());
            } else {
                multiUpload(bucket, filePath, file.getBytes());
            }

            return filePrefix+filePath;
        } catch (Exception ex){
            return "";
        }
    }

    /**
     * 完整上传不分片
     * @param bucket bucket
     * @param key 对象路径
     * @param bytes 对象数组
     */
    @Async("awsThreadPoolExecutor")
    void singleUpload(String bucket, String key, byte[] bytes){
        Long startTime = System.currentTimeMillis() / 1000;
        PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                .bucket(bucket)
                .key(key)
                .build();
        RequestBody requestBody = RequestBody.fromBytes(bytes);
        s3Client.putObject(putObjectRequest, requestBody);
        long endTime = System.currentTimeMillis() / 1000;

        log.info("上传文件(" + key + ")总计耗费时间为：" + (endTime - startTime) + " 秒");
    }

    /**
     * 分片上传对象，每片大小最小5M，最大5G，最多可分成10000片
     * @param bucket bucket
     * @param key 对象路径
     * @param bytes 对象数组
     */
    @Async("awsThreadPoolExecutor")
    void multiUpload(String bucket, String key, byte[] bytes){
        Long startTime = System.currentTimeMillis() / 1000;
        // 拆分数组
        int subSize = 8 * 1024 * 1024; // 每份数组的大小，此处为8M
        byte[][] byteArrs = FileUtil.splitByteArr(bytes, subSize);

        CreateMultipartUploadRequest uploadRequest = CreateMultipartUploadRequest.builder()
                .bucket(bucket)
                .key(key)
                .build();
        CreateMultipartUploadResponse uploadResponse = s3Client.createMultipartUpload(uploadRequest);
        String uploadId = uploadResponse.uploadId();

        int i = 1;
        String redisKey = Constants.AWS_MULTIPART + Constants.SEPARATOR + uploadId;
        SetOperations<Object, Object> redisList = redisTemplate.getOpsForSet();
        for(byte[] byteArr : byteArrs){
            // 分片对象上传
            UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                    .bucket(bucket)
                    .key(key)
                    .uploadId(uploadId)
                    .partNumber(i)
                    .build();
            String eTag = s3Client.uploadPart(uploadPartRequest, RequestBody.fromBytes(byteArr)).eTag();
            CompletedPart part = CompletedPart.builder().partNumber(i).eTag(eTag).build();
            i++;

            // redis存储分片对象
            if(!redisList.isMember(redisKey, part)){
                redisList.add(redisKey, part);
            }
        }

        // 获取redis存储的分片对象，并转换成list集合
        Set<Object> objectList = redisList.members(redisKey);
        if(objectList == null || objectList.size() <= 0) return;
        List<CompletedPart> completedPartList = objectList.parallelStream().map(item -> {
           CompletedPart part = (CompletedPart)item;
           return  part;
        }).sorted(Comparator.comparing(CompletedPart::partNumber)).collect(Collectors.toList());

        // 合并所有分片对象
        CompletedMultipartUpload completedMultipartUpload = CompletedMultipartUpload.builder().parts(completedPartList).build();
        CompleteMultipartUploadRequest completeMultipartUploadRequest = CompleteMultipartUploadRequest.builder()
                .bucket(bucket)
                .key(key)
                .uploadId(uploadId)
                .multipartUpload(completedMultipartUpload)
                .build();
        s3Client.completeMultipartUpload(completeMultipartUploadRequest);

        // 清除分片对象redis
        redisList.remove(redisKey, objectList);
        // 释放对象
        completedPartList = null;
        objectList = null;
        redisList = null;
        byteArrs = null;

        long endTime = System.currentTimeMillis() / 1000;
        log.info("分片上传文件(" + key + ")总计耗费时间为：" + (endTime - startTime) + " 秒");
    }

    /**
     * 对象下载，返回url下载地址
     * @param bucket bucket
     * @param key 对象路径
     */
    @Override
    public AjaxResult<Object> download(String bucket, String key){
        if(!StringUtils.hasText(bucket)) return AjaxResult.failed("system.parameterError");

        GetObjectRequest objectRequest = GetObjectRequest.builder().bucket(bucket).key(key).build();
        GetObjectPresignRequest objectPresignRequest = GetObjectPresignRequest.builder()
                .signatureDuration(Duration.ofMinutes(10))
                .getObjectRequest(objectRequest)
                .build();
        PresignedGetObjectRequest presignedGetObjectRequest = s3Presigner.presignGetObject(objectPresignRequest);
        String url = presignedGetObjectRequest.url().toString();

        return AjaxResult.success(url);
    }

    /**
     * 删除对象，支持批量删除
     * @param bucket bucket
     * @param keyList 多个key组成的json数组转化成list对象
     */
    @Override
    public AjaxResult<Object> delete(String bucket, List<AwsS3ObjectVO> keyList){
        if(keyList == null || keyList.size() <= 0) return AjaxResult.failed("system.parameterError");

        List<ObjectIdentifier> identifierList = new ArrayList<>();
        for(AwsS3ObjectVO vo : keyList){
            identifierList.add(ObjectIdentifier.builder().key(vo.getKey()).build());
        }

        try{
            Delete delete = Delete.builder().objects(identifierList).build();
            DeleteObjectsRequest deleteObjectRequest = DeleteObjectsRequest.builder().bucket(bucket).delete(delete).build();
            s3Client.deleteObjects(deleteObjectRequest);

            return AjaxResult.success();
        } catch (Exception ex){
            return AjaxResult.failed(ex.getMessage());
        }
    }
}
